from datetime import datetime
from typing import Sequence, Callable, List

from emi.bar.data import Interval, BarData
from emi.core.Context import Context
from emi.trade.data import TradeData, PositionData, AccountData, RequestOrder, OrderData, Direction, Offset, OpType, \
    Status, DailyResult
from emi.trade.trader import PortfolioData, CancelRequest
from emi.util import NumUtils, utils


class ITradeMarket:
    """
    交易行情市场行情数据。
    """

    def get_tick(self, symbol: str) -> BarData:
        """
         返回当天的实时tickshot（当时tick快照行情），如果当天停盘，返回None
        """
        raise NotImplementedError()

    def get_now(self)->datetime:
        raise NotImplementedError()



class ExchangeEngine:

    """
    交易引擎，用于撮合Broker和BrokerOreder，类似CTA引擎
    """

    __REQUEST_ID_GEN = 0
    __ORDER_ID_GEN = 0

    def __init__(self,portfolio:PortfolioData):
        self.portfolio = portfolio
        self.cancel_order_requests = []  ## 取消订单情况。
        self.buy_count = 0
        self.sell_count = 0
        self.request_count = 0
        self.DAY_TOKEN = 0
        #self.daily_results:[DailyResult] = []
        self.trade_order_callback = None

    def is_backtest(self)->bool:
        raise NotImplementedError()

    def now_time(self)->datetime:
        raise NotImplementedError()
        #return self.context.now()

    def set_trade_order_happen_callback(self,callaback = Callable[[datetime,Sequence[TradeData]],None]):
        """
        成交记录回调处理
        """
        self.trade_order_callback = callaback

    def log(self,msg:str):
        print(msg)
        #return self.context.log_i("ExchangeEngine",msg)

    def update_single_position_price(self, position:PositionData, market:ITradeMarket)->bool:
        """
          获得当前的持仓的实时价格。
          """
        raise NotImplementedError()

    def cal_cross_order_price(self, market:ITradeMarket, order:OrderData, time:datetime)->float:
        """
        计算交割价格。如果不能成交，返回None
        """
        raise NotImplementedError()

    def get_account_data(self)->AccountData:
        ##返回当前资金状态。
        return self.portfolio.to_account_data()

    def build_request_order(self, symbol: str, price: float, volume: float,op_type:OpType,immediately:bool,source:str = None) -> RequestOrder:
        id = f"{ExchangeEngine.__REQUEST_ID_GEN}"
        ExchangeEngine.__REQUEST_ID_GEN +=1
        return RequestOrder(id=id,symbol=symbol,price=price,volume=volume,op_type=op_type,
                            immediately=immediately,
                            source=source,

                            )

    def send_cancel_request(self,order_id = None,syomol = None,source = None):
        cancel_request = CancelRequest(orderid=order_id,symbol=syomol,source = source)
        self.cancel_order_requests.insert(0,cancel_request)

    def send_order_request(self, request_order:RequestOrder)->OrderData:
        """
        提及订单
        """
        order = OrderData(
            symbol=request_order.symbol,
            direction=request_order.direction,
            offset=request_order.offset,
            type=request_order.type,
            price=request_order.price,
            volume=request_order.volume,
            msg=request_order.msg,
            op_type=request_order.op_type,
            source=request_order.source,
            immediately=request_order.immediately,
        )
        order.create_time = request_order.create_time
        order.update_time = request_order.create_time
        old_account = self.portfolio.to_account_data()
        self.log(f" 新订单：symbol:{order.symbol} , price:{order.price},volume= {order.volume} ,({order.direction.name},{order.offset.name},immediately:{order.immediately})")
        self.on_sumbit_order(self.portfolio,order)
        self.check_balance_if_correct(old_account,"on_sumbit_order")
        if order.status is None:
            order.status = Status.REJECTED
            order.description = '下单未处理'
        if order.is_finished():
            self.log(
                f" 新订单提交失败: {order.description}")

        return order


    def _handle_cancel_orders(self,time:datetime):
        while len(self.cancel_order_requests) > 0:
            canel_request: CancelRequest = self.cancel_order_requests.pop()
            if not canel_request.orderid is None:
                for oreder in self.portfolio.orders:
                    if oreder.id == canel_request.orderid:
                        old_account = self.portfolio.to_account_data()
                        self.on_cancel_order(self.portfolio, time, oreder)
                        self.check_balance_if_correct(old_account, "_handle_cancel_orders()")
                        break
            elif not canel_request.symbol is None:
                for oreder in list(self.portfolio.orders):
                    if oreder.symbol == canel_request.symbol:
                        old_account = self.portfolio.to_account_data()
                        self.on_cancel_order(self.portfolio, time, oreder)
                        self.check_balance_if_correct(old_account, "_handle_cancel_orders()")
            elif not canel_request.source is None:
                for oreder in list(self.portfolio.orders):
                    if oreder.source == canel_request.source:
                        old_account = self.portfolio.to_account_data()
                        self.on_cancel_order(self.portfolio, time, oreder)
                        self.check_balance_if_correct(old_account, "_handle_cancel_orders()")
            else:
                for oreder in list(self.portfolio.orders):
                    old_account = self.portfolio.to_account_data()
                    self.on_cancel_order(self.portfolio, time, oreder)
                    self.check_balance_if_correct(old_account, "_handle_cancel_orders()")

    def process_tick_data(self, market:ITradeMarket, time:datetime):
        """
        处理实时行情
        """
        self._handle_cancel_orders(time)

        ## 处理订单
        trader_orders = self.on_process_profolio_orders(self.portfolio,market,time)
        if len(trader_orders) > 0:
            self.on_trade_data_happen(trader_orders)

    def on_trade_data_happen(self, trade_datas: Sequence[TradeData]):
        """
        成交记录发生
        """
        today_daily_result = self.portfolio.today_daily_result
        assert not today_daily_result is None
        today_daily_result.tradeDatas.extend(trade_datas)
        if not self.trade_order_callback is None:
            self.trade_order_callback(self.now_time(),trade_datas)

    def _handle_update_position_price(self, market:ITradeMarket, time:datetime):
        ### 更新持仓价格
        for position in self.portfolio.long_positions.values():
            position:PositionData = position
            if position.volume > 0.001:
                is_update = self.update_single_position_price(position, market)
                if is_update:
                    position.update = time

        for position in self.portfolio.short_positions.values():
            position: PositionData = position
            if position.volume > 0.001:
                is_update =  self.update_single_position_price(position, market)
                if is_update:
                    position.update = time

    def forzen_money_while_buy(self)->bool:
        """
        买入挂单时（还未成交），是否需要预选冻结资金。
        """
        return False

    def on_sumbit_order(self,portfolio:PortfolioData,order:OrderData):
        """
        处理订单的提交。
        """
        assert order.status is None
        ##检测是否有可用资金
        need_commission = portfolio.get_commission(order.price,order.volume)
        need_money = NumUtils.ceil(need_commission)+ portfolio.get_balance_money(order.price, order.volume)

        if order.direction == Direction.LONG:
            if order.offset == Offset.OPEN:
                self.request_count +=1
                if portfolio.available >= need_money:
                    order.status = Status.SUBMITTING

                    order.need_open_money = need_money
                    if self.forzen_money_while_buy():
                        ## 冻结买入的资金
                        ##更新账面资产情况
                        portfolio.available -= need_money
                        order.frozen_money = need_money
                    else:
                        order.frozen_money = 0

                    portfolio.orders.append(order)
                else:
                    order.status = Status.REJECTED
                    order.description = f"金额不足: 需要{need_money:.2f},可用:{portfolio.available:.2f}"

            elif order.offset == Offset.CLOSE:
                self.request_count += 1
                position = portfolio.get_position(symobl=order.symbol,direction=order.direction)
                avaliable_volume = position.volume_available
                if avaliable_volume >= order.volume:
                    order.status = Status.SUBMITTING
                    position.volume_frozen += order.volume ## 冻结仓位
                    position.volume_available -= order.volume ## 冻结仓位
                    portfolio.orders.append(order)

                else:
                    order.status = Status.REJECTED
                    order.description = f"可用仓位不足: 需要{order.volume:.1f},可用:{avaliable_volume:.1f}"

            else:
                raise NotImplementedError()
        else:
            raise NotImplementedError()



    def on_process_profolio_orders(self, portfolio:PortfolioData, market:ITradeMarket, time:datetime)->Sequence[TradeData]:
        """
        处理账面资产对象portfolio里的未完成的订单
        """
        trade_oreder_list = []  ##成交记录
        for order in list(portfolio.orders):
            order:OrderData = order
            assert not order.is_finished()
            assert order.update_time <= time
            order.update_time = time

            ## 计算成交价格
            trade_price = self.cal_cross_order_price(market, order, time)
            if not trade_price is None :
                ## 可以成交
                if order.direction == Direction.LONG:
                    if order.offset == Offset.OPEN :
                        ##再次检测，当前可用资金是否足额。
                        ##买入成功
                        ## 订单情况变化

                        ### 统一持有仓位的最新价格,方便检查市值
                        position = portfolio.get_position(symobl=order.symbol,direction=order.direction)
                        position.price_cur = trade_price  ##
                        old_account = self.portfolio.to_account_data()

                        commission = portfolio.get_commission(trade_price, order.volume)  ## 交易佣金
                        trade_money = portfolio.get_balance_money(trade_price, order.volume)  ## 成交市值
                        if self.forzen_money_while_buy():
                            ## 买入时已经预选冻结资金了。
                            assert order.frozen_money > 0
                            extra_money = order.need_open_money - commission - trade_money  ## 多余的金额
                            assert extra_money >= 0
                            order.status = Status.ALLTRADED
                        else:
                            assert order.frozen_money == 0
                            if commission + trade_money > portfolio.available:
                                ##没有足够资金买入
                                order.status = Status.REJECTED
                                order.description = f"金额不足: 需要{order.need_open_money:.2f},可用:{portfolio.available:.2f}"
                                portfolio.orders.remove(order)
                                self.log(
                                    f"取消订单:金额不足：symbol={order.symbol},direction={order.direction},offset={order.offset}, price:{order.price},"
                                    f"volume= {order.volume},需要{order.need_open_money:.2f},可用:{portfolio.available:.2f}")
                                continue
                            else:
                                portfolio.available-= (commission+trade_money)
                                extra_money = 0


                        ## 成交记录
                        trade_order = TradeData(orderid=order.id, day_token=self.DAY_TOKEN,symbol=order.symbol, direction=order.direction,
                                                offset=order.offset,time = time,msg = order.msg,source=order.source)
                        trade_order.op_type = order.op_type
                        trade_order.price = trade_price
                        trade_order.trade_money = trade_money
                        trade_order.volume = order.volume
                        trade_order.commission = commission
                        trade_oreder_list.append(trade_order)

                        ##更新账户资仓情况
                        old_pos_money = portfolio.get_position_balance(position)
                        portfolio.update_posisiton(self,trade_order)
                        new_pos_money = portfolio.get_position_balance(position)
                        assert abs(new_pos_money - old_pos_money - trade_money ) < 0.1
                        order.status = Status.ALLTRADED
                        portfolio.orders.remove(order)
                        portfolio.available += extra_money
                        portfolio.commission += commission

                        self.check_balance_if_correct(old_account, "做多买入")
                        self.buy_count +=1


                    elif order.offset == Offset.CLOSE :
                        ## 卖出成功

                        ### 统一持有仓位的最新价格,方便检查市值
                        position = portfolio.get_position(symobl=order.symbol, direction=order.direction)
                        position.price_cur = trade_price  ##
                        old_account = self.portfolio.to_account_data()

                        commission = portfolio.get_commission(trade_price, order.volume)  ## 交易佣金
                        trade_money = portfolio.get_balance_money(trade_price, order.volume)  ## 成交金额
                        ## 成交记录
                        trade_order = TradeData(orderid=order.id, day_token=self.DAY_TOKEN, symbol=order.symbol, direction=order.direction,
                                                offset=order.offset,time=time,msg = order.msg,source=order.source)
                        trade_order.op_type = order.op_type
                        trade_order.price = trade_price
                        trade_order.trade_money = trade_money
                        trade_order.commission = commission
                        trade_order.volume = order.volume

                        trade_oreder_list.append(trade_order)

                        portfolio.update_posisiton(self,trade_order)
                        order.status = Status.ALLTRADED
                        portfolio.orders.remove(order)
                        portfolio.available += trade_money - commission
                        portfolio.commission += commission

                        self.check_balance_if_correct(old_account, "做多卖出")
                        self.sell_count += 1

                    else:
                        raise NotImplementedError()
                else:
                    raise NotImplementedError()
        return trade_oreder_list


    def on_cancel_order(self,portfolio:PortfolioData,time:datetime,order:OrderData):
        self.log(f"取消订单：symbol={order.symbol},direction={order.direction},offset={order.offset}, price:{order.price},volume= {order.volume}")

        if order.direction == Direction.LONG:
            if order.offset == Offset.OPEN:
                ##买入订单
                order.status = Status.CANCELLED
                portfolio.orders.remove(order)
                portfolio.available += order.frozen_money

            elif order.offset == Offset.CLOSE:
                ## 卖出订单
                order.status = Status.CANCELLED
                portfolio.orders.remove(order)
                position = portfolio.get_position(order.symbol,order.direction)
                position.volume_frozen -= order.volume
                position.volume_available += order.volume

            else:
                raise NotImplementedError()
        else:
            raise NotImplementedError()

    def on_day_begin(self,time:datetime):
        ## 重置资产状态。
        self.DAY_TOKEN += 1  ## 交易日日期令牌
        today_daily_result = self.portfolio.today_daily_result
        ### 按道理today_daily_result是为Node，如果不为None，如果实盘运行中间中断，可能今天继续运行，也可以明天运行。
        if not today_daily_result is None:
            assert today_daily_result.time < time
            assert self.is_backtest() == False
            if utils.is_same_day(time,today_daily_result.time):
                ##实盘今天继续运行
                self.log("引擎继续今天的实盘状态")
            else:
                self.log(f"{today_daily_result.time}的日报数据未正常结束，实盘数据可能有确实！！！")
            self.on_daily_result_finished(today_daily_result)
            self.portfolio.today_daily_result = None
            today_daily_result = None

        assert today_daily_result is None
        self.portfolio.today_daily_result = DailyResult(time,day_token=self.DAY_TOKEN)
        old_account = self.portfolio.to_account_data()
        self.portfolio.resetWithNewDay(time)
        if abs(old_account.balance - self.portfolio.balance)> 0.01:
            raise RuntimeError(f"账面市值金额变化浮动比较大:on_day_begin()")


    def on_day_end(self,time:datetime):

        old_account = self.portfolio.to_account_data()
        self.send_cancel_request(order_id=None,syomol=None)
        self._handle_cancel_orders(time)
        assert len(self.portfolio.orders) == 0
        self.check_balance_if_correct(old_account, "on_day_end()")

        ##交割并计算今天的daily_result
        today_daily_result = self.portfolio.today_daily_result
        if self.is_backtest():
            assert not today_daily_result is None
        elif today_daily_result is None:
            return
        today_daily_result.balance = self.portfolio.to_account_data().balance
        today_daily_result.balance_hold = self.portfolio.get_balance_from_positon()
        today_daily_result.commission =  self.portfolio.commission
        today_commision = 0
        for trade_data in today_daily_result.tradeDatas:
            today_commision += trade_data.commission
        assert NumUtils.is_equal(today_commision, today_daily_result.commission)
        self.on_daily_result_finished(today_daily_result)
        self.portfolio.today_daily_result = None

        ##self.portfolio.print_position()

    def update_position_balance(self, time:datetime, market:ITradeMarket):
        self._handle_update_position_price(market,time)

    def on_daily_result_finished(self,result:DailyResult):
        self.portfolio.daily_results.append(result)

    def check_balance_if_correct(self,old_account:AccountData,call_method = ""):
        """
        检测目前账面资金市值情况。（一般来说，账面资金状态发生变化时，资金市值的变化是很小的）
        """
        new_account = self.portfolio.to_account_data()

        ##资产状况变化产生的佣金费用
        occur_commission =  new_account._commission - old_account._commission
        assert occur_commission >= 0
        if abs(old_account.balance - new_account.balance - occur_commission)> 0.01:
            print(
                f" old :{old_account},float:{old_account.float_blance}\n"
                f" new :{new_account},float:{old_account.float_blance}\n"
                f" account status（{call_method}）: 变化前市值 - 变化后市值 - 当中产生费用 = {old_account.balance}- {new_account.balance} -{occur_commission} =  "
                f"{old_account.balance - new_account.balance - occur_commission}")
            raise RuntimeError(f"{call_method}:账面市值金额变化浮动比较大，请检测逻辑是否正确！！old:{old_account.balance},"
                               f"new:{new_account.balance},commision:{occur_commission}")



class ContextExchangeEngine(ExchangeEngine):
    """
    根据实时行情交易
    """

    def __init__(self,context:Context, portfolio: PortfolioData,market:ITradeMarket,interval:Interval = Interval.DAILY):
        super().__init__(portfolio)
        self.context = context
        self.market = market
        self.interval = interval


    def is_backtest(self)->bool:
        return True

    def now_time(self) -> datetime:
        return self.market.get_now()

    def log(self, msg: str):
        print(f"[{self.now_time()}]: {msg}")

    def cal_cross_order_price(self, market:ITradeMarket, order:OrderData, time:datetime)->float:

        if self.interval < Interval.DAILY and not order.immediately:
            ### 日级别以下的频率的订单默认是由挂单交易：产生订单的时间在前。
            if time <= order.create_time:
                return None


        is_waitting_order = not order.immediately and self.interval < Interval.DAILY  ##  是否挂单交易

        self._the_time = time
        cur_tick = market.get_tick(symbol=order.symbol)
        if not cur_tick is None and cur_tick.volume > 0:
            ## 判断是否可以成交
            if order.direction == Direction.LONG:

                if order.price is None:
                    ### 市价（现价）成交
                    return cur_tick.close

                if order.offset == Offset.OPEN:
                    trade_price = order.price  ##成交价
                    if trade_price < cur_tick.low:
                        ## 未达到价格，跳过
                        return None
                    return trade_price
                elif order.offset == Offset.CLOSE:
                    trade_price = order.price  ##成交价
                    if trade_price > cur_tick.high:
                        return None
                    if not is_waitting_order:
                        ### 马上交易
                        return trade_price
                    else:
                        if cur_tick.open >= trade_price:
                            return cur_tick.open
                        if trade_price <= cur_tick.high:
                            return trade_price
                        return None
            raise NotImplementedError()
        return None

    def update_single_position_price(self, position: PositionData, market: ITradeMarket):
        tick = market.get_tick(position.symbol)
        if tick is None:
            return False
        position.price_cur = tick.close
        return True


class SimpleExchangeEngine(ExchangeEngine):
    """
    简单交易引擎。总是能够交易。
    """
    def __init__(self,portfolio: PortfolioData,print_log = False):
        super().__init__(portfolio)
        self.print_log = print_log
        self._the_time = datetime(year=1970,month=1,day=1)

    def is_backtest(self)->bool:
        return True

    def log(self, msg: str):
        if self.print_log:
            super().log(msg)

    def now_time(self) -> datetime:
        return self._the_time

    def on_day_begin(self, time: datetime):
        self._the_time = time
        super().on_day_begin(time)

    def on_day_end(self, time: datetime):
        self._the_time = time
        super().on_day_end(time)

    def update_position_balance(self, time:datetime, market:ITradeMarket):
        self._the_time = time
        super().update_position_balance(time,market)


    def cal_cross_order_price(self, market:ITradeMarket, order:OrderData, time:datetime):
        self._the_time = time
        return order.price

    def update_single_position_price(self, position:PositionData, market:ITradeMarket):
        return True

    def send_order_request(self, request_order: RequestOrder) -> OrderData:
        order = super().send_order_request(request_order)
        ## 提交完订单之后马上更新成交情况
        self.process_tick_data(None,request_order.create_time)
        return order



